/*
 * PC_MiniLog.c
 *      Author: DePhille
 */


#include "Log_Mini.h"


/**
 * Creates a new miniature log struct.
 *
 * @returns The new MiniLog or NULL on failure.
 */
static PC_MiniLog* PC_MiniLog_New(void) {
	PC_MiniLog*	retLog;	// Clean-up: No

	// Allocate Minilog:
	retLog = g_new(PC_MiniLog, 1);
	if (!retLog) {
		purple_debug_error("PidginCote", "PC_MiniLog_New: Unable to allocate MiniLog object.\r\n");
		return NULL;
	}

	// Assign default values:
	retLog->fileName = NULL;
	retLog->lastChange = 0;

	// Return log:
	return retLog;
}



/**
 * Deletes a miniature log from memory.
 *
 * @param	miniLog The MiniLog to delete.
 */
static void PC_MiniLog_Delete(PC_MiniLog* miniLog) {
	// Check input:
	if (!miniLog) {
		purple_debug_warning("PidginCote", "PC_MiniLog_Delete: Missing 'miniLog' parameter.\r\n");
		return;
	}

	// Delete data members:
	g_free(miniLog->fileName);
	miniLog->fileName = NULL;

	// Delete entire struct:
	g_free(miniLog);
}

/**
 * Recursively fetches all filenames as MiniLogs in a given directory and stores them alphabetically in the given GList.
 *
 * @param	path	The path under which to collect the filenames.
 * @param	retList	The GList in which to store the sorted MiniLogs.
 */
static void PC_MiniLog_ScanDir(const gchar* path, GList** retList) {
	GDir*			dir;		// Clean-up: Yes
	const gchar*	curName;	// Clean-up: No
	gchar*			curPath;	// Clean-up: Yes/No
	PC_MiniLog*		miniLog;	// Clean-up: No


	// Check input:
	if (!path) {
		purple_debug_warning("PidginCote", "PC_MiniLog_ScanDir: Missing 'path' parameter.\r\n");
		return;
	} else if (!retList) {
		purple_debug_warning("PidginCote", "PC_MiniLog_ScanDir: Missing 'retList' parameter.\r\n");
		return;
	}

	// Open the path:
	dir = g_dir_open(path, 0, NULL);
	if (!dir) {
		purple_debug_info("PidginCote", "PC_MiniLog_ScanDir: Unable to open dir %s\r\n", path);
		return;
	}

	// Traverse the dir:
	curName = g_dir_read_name(dir);
	while (curName) {
		// Transform name into path:
		curPath = g_strdup_printf("%s/%s", path, curName);

		// Check if it is a directory:
		if (g_file_test(curPath, G_FILE_TEST_IS_DIR)) {
			// Recursive call:
			PC_MiniLog_ScanDir(curPath, retList);

			// Clean-up:
			g_free(curPath);
			curPath = NULL;
		} else {
			// Make MiniLog:
			miniLog = PC_MiniLog_New();
			if (!miniLog) {
				purple_debug_error("PidginCote", "PC_MiniLog_ScanDir: Unable to build MiniLog.\r\n");
			} else {
				// Assign variables:
				miniLog->fileName = curPath;

				// Add to list:
				*retList = g_list_insert_sorted(*retList, miniLog, PC_MiniLog_CompareFunc);
			}
		}

		// Next name:
		curName = g_dir_read_name(dir);
	}

	// Close dir:
	g_dir_close(dir);
}


/**
 * Compares two MiniLogs.
 * 	See GCompareFunc
 */
static gint PC_MiniLog_CompareFunc(gconstpointer a, gconstpointer b) {
	const PC_MiniLog*	log1;	// Clean-up: No
	const PC_MiniLog*	log2;	// Clean-up: No

	// Cast:
	log1 = (const PC_MiniLog*) a;
	log2 = (const PC_MiniLog*) b;

	// Check input:
	if (log1 == NULL) {
		purple_debug_warning("PidginCote", "PC_MiniLog_CompareFunc: Missing or invalid 'log1' parameter.\r\n");
		return -1;	// a<b
	} else if (log2 == NULL) {
		purple_debug_warning("PidginCote", "PC_MiniLog_CompareFunc: Missing or invalid 'log2' parameter.\r\n");
		return 1;	// a>b
	}

	// Compare filenames:
	return g_strcmp0(log1->fileName, log2->fileName);
}



/**
 * TODO: Remove debugging function.
 */
static void PC_MiniLog_Print(const GList* list) {
	const GList*		curItem;	// Clean-up: No
	PC_MiniLog*			curLog;		// Clean-up: No

	// Check input:
	if (!list) {
		purple_debug_warning("PidginCote", "PC_MiniLog_Print: Missing 'list' parameter.\r\n");
		return;
	}

	// Traverse:
	curItem = list;
	while (curItem) {
		// Cast:
		curLog = (PC_MiniLog*) curItem->data;
		if (!curLog) {
			purple_debug_error("PidginCote", "PC_MiniLog_Print: Invalid element detected.\r\n");
		}

		// Next:
		curItem = g_list_next(curItem);
	}
}



/**
 * Extracts common entries from two alphabetically sorted lists.
 *
 * @param	dbOnly	A GList of MiniLogs in the database.
 * @param	fsOnly	A GList of MiniLogs on the FileSystem.
 * @returns	A list of common MiniLogs.
 */
static GList* PC_MiniLog_Filter(GList** dbOnly, GList** fsOnly) {
	GList*		common;		// Clean-up: No
	GList*		curDB;		// Clean-up: No
	GList*		curFS;		// Clean-up: No
	GList*		tmp;		// Clean-up: No
	PC_MiniLog*	curDBLog;	// Clean-up: No
	PC_MiniLog*	curFSLog;	// Clean-up: No
	int			cmp;		// Clean-up: No

	// Check input:
	if (!dbOnly) {
		purple_debug_warning("PidginCote", "PC_MiniLog_Filter: Missing 'dbOnly' parameter.\r\n");
		return NULL;
	} else if (!fsOnly) {
		purple_debug_warning("PidginCote", "PC_MiniLog_Filter: Missing 'fsOnly' parameter.\r\n");
		return NULL;
	}

	// Compare items:
	curDB = *dbOnly;
	curFS = *fsOnly;
	curDBLog = NULL;
	curFSLog = NULL;
	common = NULL;
	while (curDB && curFS) {
		// Cast:
		if (!curDBLog) {
			curDBLog = (PC_MiniLog*) curDB->data;
		}
		if (!curFSLog) {
			curFSLog = (PC_MiniLog*) curFS->data;
		}

		// Check casts:
		if (!curDBLog) {
			purple_debug_warning("PidginCote", "PC_MiniLog_Filter: Invalid element detected in dbOnly list.\r\n");

			// Skip entry:
			curDB = g_list_next(curDB);
		} else if (!curFSLog) {
			purple_debug_warning("PidginCote", "PC_MiniLog_Filter: Invalid element detected in fsOnly list.\r\n");

			// Skip entry:
			curFS = g_list_next(curFS);
		} else {
			// Compare fileName:
			cmp = g_strcmp0(curDBLog->fileName, curFSLog->fileName);
			if (cmp == 0) {
				// Add to common list:
				common = g_list_prepend(common, curDBLog);

				// Remove from DBOnly list and advance:
				curDB->data = NULL;
				tmp = curDB;
				curDB = g_list_next(curDB);
				*dbOnly = g_list_delete_link(*dbOnly, tmp);
				curDBLog = NULL;

				// Remove from FSOnly list and advance:
				curFS->data = NULL;
				tmp = curFS;
				curFS = g_list_next(curFS);
				*fsOnly = g_list_delete_link(*fsOnly, tmp);
				curFSLog = NULL;
			} else if (cmp < 0) {
				// Advance CurDB list:
				curDB = g_list_next(curDB);
				curDBLog = NULL;
			} else if (cmp > 0) {
				// Advance CurFS list:
				curFS = g_list_next(curFS);
				curFSLog = NULL;
			}
		}
	}

	// Return common list:
	return common;
}



/**
 * Checks if the given minilog has been changed on the filesystem.
 * 	This is done by comparing the miniLog's lastChanged attribute with the FileSystem's last modified attribute.
 *
 * @param	miniLog	The miniLog to check.
 * @returns	True if the miniLog was changed, false otherwise.
 */
static gboolean		PC_MiniLog_HasChanged(PC_MiniLog* miniLog) {
	time_t	mTime;	// Clean-up: No

	// Check input:
	if (!miniLog) {
		purple_debug_warning("PidginCote", "PC_MiniLog_HasChanged: Missing 'miniLog' parameter.\r\n");
		return FALSE;
	} else if (!miniLog->fileName) {
		purple_debug_warning("PidginCote", "PC_MiniLog_HasChanged: Missing 'miniLog->fileName' parameter.\r\n");
		return FALSE;
	}

	// Fetch FS mTime:
	mTime = PC_Util_GetMTime(miniLog->fileName);
	if (mTime <= 0) {
		purple_debug_error("PidginCote", "PC_MiniLog_HasChanged: Unable to fetch MTime from file.\r\n");
		return FALSE;
	}

	// Compare:
	if (mTime == miniLog->lastChange) {
		return FALSE;
	} else if (mTime > miniLog->lastChange) {
		return TRUE;
	} else {
		purple_debug_warning("PidginCote", "PC_MiniLog_HasChanged: Given log is newer than the one on the fileSYstem?\r\n");
		return TRUE;
	}
}
